package cn.uncode.rpc.cluster.ha;

import java.util.ArrayList;
import java.util.List;

import cn.uncode.rpc.cluster.LoadBalance;
import cn.uncode.rpc.common.log.Logger;
import cn.uncode.rpc.common.log.LoggerFactory;
import cn.uncode.rpc.core.Invoker;
import cn.uncode.rpc.core.Request;
import cn.uncode.rpc.core.Response;
import cn.uncode.rpc.core.URL;
import cn.uncode.rpc.core.URLParam;
import cn.uncode.rpc.exception.ClusterException;
import cn.uncode.rpc.spi.SpiMeta;
import cn.uncode.rpc.util.ExceptionUtil;

/**
 * Failover ha strategy.
 */
@SpiMeta(name = "failover")
public class FailoverHaStrategy<T> extends AbstractHaStrategy<T> {
	
	private static final Logger LOGGER = LoggerFactory.getLogger(FailoverHaStrategy.class);

    protected ThreadLocal<List<Invoker<T>>> invokersHolder = new ThreadLocal<List<Invoker<T>>>() {
        @Override
        protected List<Invoker<T>> initialValue() {
            return new ArrayList<Invoker<T>>();
        }
    };

    @Override
    public Response call(Request request, LoadBalance<T> loadBalance) {

        List<Invoker<T>> invokers = selectInvokers(request, loadBalance);
        if (invokers.isEmpty()) {
            throw new ClusterException(String.format("FailoverHaStrategy No invokers for request:%s, loadbalance:%s", request, loadBalance));
        }
        URL refUrl = invokers.get(0).getUrl();
        // 先使用method的配置
        int tryCount = refUrl.getMethodParameter(request.getMethodName(), request.getParamtersDesc(), 
        		URLParam.RETRIES.getName(), URLParam.RETRIES.getIntValue());
        // 如果有问题，则设置为不重试
        if (tryCount < 0) {
            tryCount = 0;
        }

        for (int i = 0; i <= tryCount; i++) {
        	Invoker<T> invoker = invokers.get(i % invokers.size());
            try {
                request.setRetries(i);
                return invoker.invoke(request);
            } catch (RuntimeException e) {
                // 对于业务异常，直接抛出
                if (ExceptionUtil.isBizException(e)) {
                    throw e;
                } else if (i >= tryCount) {
                    throw e;
                }
                LOGGER.warn(String.format("FailoverHaStrategy Call false for request:%s error=%s", request, e.getMessage()));
            }
        }

        throw new ClusterException("FailoverHaStrategy.call should not come here!");
    }

    protected List<Invoker<T>> selectInvokers(Request request, LoadBalance<T> loadBalance) {
        List<Invoker<T>> invoker = invokersHolder.get();
        invoker.clear();
        loadBalance.selectToHolder(request, invoker);
        return invoker;
    }

}
